/*****************************************************************************/
/**
*
* @file adau1761.c
*
* User defined functions about adau1761.
*
* <pre>
* MODIFICATION HISTORY:
*
* Ver   Who    Date   	Changes
* ----- ---- ---------- -------------------------------------------------------
* 0.01  abu  05/31/2019 Created
* </pre>
*
*------------------------------------------------------------------------------
* Author: abu	E-mail:	abu_liu@aliyun.com
*				Blog:	https://blog.csdn.net/ClamerKorallen
*				Gitee:	https://gitee.com/abu_liu
*------------------------------------------------------------------------------
*
******************************************************************************/
#include "adau1761.h"
#include "xiicps.h"


/*****************************************************************************/
/**
*
* read one register and return its value
*
* @param	reg_addr_offset is the offset address of the specified register.
*
* @return	register value.
*
* @note		None.
*
******************************************************************************/
u8 Adau1761_ReadReg(u8 reg_addr_offset)
{
	u8 reg_addr[ADAU1761_SUBADDR_BYTENUM];
	u32 reg_val_tmp = 0x00;
	u8 reg_val = 0x00;
	u8 pll_status_regval[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

	/*
	 * Check the parameters
	 */
	DEBUG_CHECK(reg_addr_offset <=ADAU1761_REG_NUM);	
	
	while(XIicPs_BusIsBusy(ADAU1761_CPU_IIC_PTR) == TRUE)
	{
		//NOP
	}

	if((reg_addr_offset == 0x00) || (reg_addr_offset > 0x07))
	{
		//copy subaddress to a new array
		reg_addr[0U] = ADAU1761_SUBADDR_HIGH;
		reg_addr[1U] = ADAU1761_SUBADDR_LOW + reg_addr_offset;
		
		uIic0TransStatus.SendComplete = FALSE;
		XIicPs_MasterSend(ADAU1761_CPU_IIC_PTR, reg_addr, ADAU1761_SUBADDR_BYTENUM,
							(u16)ADAU1761_CHIP_ADDR);
		while (!uIic0TransStatus.SendComplete)
		{
			if (0 != uIic0TransStatus.TotalErrorCount)
			{
				DEBUG("Event is 0x%x\n\r", XIicPs_ReadReg(uiic0ps.Config.BaseAddress,
						XIICPS_IMR_OFFSET));
				DEBUG_POS();
			}
		}
		
		while(XIicPs_BusIsBusy(ADAU1761_CPU_IIC_PTR) == TRUE)
		{
			//NOP
		}
		
		//clear iic data fifo
		reg_val_tmp = XIicPs_ReadReg(ADAU1761_CPU_IIC_PTR->Config.BaseAddress, 0x00);
		XIicPs_WriteReg(ADAU1761_CPU_IIC_PTR->Config.BaseAddress, 0x00, (reg_val_tmp | 0x0040));

		uIic0TransStatus.RecvComplete = FALSE;
		if((reg_addr_offset == 0x00) || (reg_addr_offset > 0x07))
		{
			XIicPs_MasterRecv(ADAU1761_CPU_IIC_PTR, &reg_val, 1U,
						ADAU1761_CHIP_ADDR);
			while (!uIic0TransStatus.RecvComplete)
			{
				if (0 != uIic0TransStatus.TotalErrorCount)
					{
						DEBUG("Event is 0x%x\n\r", XIicPs_ReadReg(uiic0ps.Config.BaseAddress, XIICPS_IMR_OFFSET));
						DEBUG_POS();
					}
			}
			return reg_val;
		}

	}
	else
	{
		//copy subaddress to a new array
		reg_addr[0U] = ADAU1761_SUBADDR_HIGH;
		reg_addr[1U] = ADAU1761_SUBADDR_LOW + ADAU1761_PLL_REGADDR;

		uIic0TransStatus.SendComplete = FALSE;
		XIicPs_MasterSend(ADAU1761_CPU_IIC_PTR, reg_addr, ADAU1761_SUBADDR_BYTENUM,
							(u16)ADAU1761_CHIP_ADDR);
		while (!uIic0TransStatus.SendComplete)
		{
			if (0 != uIic0TransStatus.TotalErrorCount)
			{
				DEBUG("Event is 0x%x\n\r", XIicPs_ReadReg(uiic0ps.Config.BaseAddress,
						XIICPS_IMR_OFFSET));
				DEBUG_POS();
			}
		}
		
		while(XIicPs_BusIsBusy(ADAU1761_CPU_IIC_PTR) == TRUE)
		{
			//NOP
		}
		
		//clear iic data fifo
		reg_val_tmp = XIicPs_ReadReg(ADAU1761_CPU_IIC_PTR->Config.BaseAddress, 0x00);
		XIicPs_WriteReg(ADAU1761_CPU_IIC_PTR->Config.BaseAddress, 0x00, (reg_val_tmp | 0x0040));

		uIic0TransStatus.RecvComplete = FALSE;
		XIicPs_MasterRecv(ADAU1761_CPU_IIC_PTR, pll_status_regval, 6,
					(u16)ADAU1761_CHIP_ADDR);
		while (!uIic0TransStatus.RecvComplete)
		{
			if (0 != uIic0TransStatus.TotalErrorCount)
				{
					DEBUG("Event is 0x%x\n\r", XIicPs_ReadReg(uiic0ps.Config.BaseAddress, XIICPS_IMR_OFFSET));
					DEBUG_POS();
				}
		}
		reg_val = pll_status_regval[reg_addr_offset - (u8)ADAU1761_PLL_REGADDR];
		return reg_val;
	}
	
}

/*****************************************************************************/
/**
*
* write one byte to a specified register
*
* @param	reg_addr_offset is the offset address of the specified register.
* @param	reg_val is the value to be written to the specified register
*
* @return	None.
*
* @note		None.
*
******************************************************************************/
void Adau1761_WriteReg(u8 reg_addr_offset, u8 reg_val)
{
	u8 data[3];

	//
	DEBUG_CHECK(reg_addr_offset <= ADAU1761_REG_NUM);
	
	//pll registers are forbidden to be written in single byte mode
	//the 6 pll reigsters from 0x02 to 0x07 should be written in continuous mode
	DEBUG_CHECK((reg_addr_offset == 0) || (reg_addr_offset >= 0x08));

	
	//copy datas to a new array
	data[0U] = (u8)ADAU1761_SUBADDR_HIGH;
	data[1U] = reg_addr_offset;
	data[2U] = reg_val;

	/*
	 * make sure that the iic device is not busy before changing register settings.
	 */
	while(XIicPs_BusIsBusy(ADAU1761_CPU_IIC_PTR) == TRUE)
	{
		//NOP
	}

	uIic0TransStatus.SendComplete = FALSE;
	XIicPs_MasterSend(ADAU1761_CPU_IIC_PTR, data, 3U, (u16)ADAU1761_CHIP_ADDR);
	while (!uIic0TransStatus.SendComplete) {
		if (0 != uIic0TransStatus.TotalErrorCount)
		{
			DEBUG("Event is 0x%x\n\r", XIicPs_ReadReg(uiic0ps.Config.BaseAddress, XIICPS_IMR_OFFSET));
			DEBUG_POS();
		}
	}

}


/*****************************************************************************/
/**
*
* write bytes to specified registers
*
*
* @param	reg_addr_offset is the offset address of the starting register.
* @param	value is the point of the value that will be written to chip
* @param	bytecnt is the byte number of value
*
* @return	None.
*
* @note		None.
*
******************************************************************************/
void Adau1761_WriteBytes(u8 reg_addr_offset, u8 *value, s32 bytecnt)
{
	u32 i;
	u8 data[bytecnt+2];

	//DEBUG_CHECK(reg_addr_offset <=ADAU1761_REG_NUM);	
	//DEBUG_CHECK(bytecnt <= ADAU1761_REG_NUM - reg_addr_offset);

	//copy datas to a new array
	data[0U] = (u8)ADAU1761_SUBADDR_HIGH;
	data[1U] = reg_addr_offset;
	for(i=2U;i<bytecnt+2U;i++)
	{
		data[i] = *value;
		value ++;
	}

	/*
	 * make sure that the iic device is not busy before changing register settings.
	 */
	while(XIicPs_BusIsBusy(ADAU1761_CPU_IIC_PTR) == TRUE)
	{
		//NOP
	}

	uIic0TransStatus.SendComplete = FALSE;
	XIicPs_MasterSend(ADAU1761_CPU_IIC_PTR, data, bytecnt+2U, (u16)ADAU1761_CHIP_ADDR);
	while (!uIic0TransStatus.SendComplete) {
		if (0 != uIic0TransStatus.TotalErrorCount)
		{
			DEBUG("Event is 0x%x\n\r", XIicPs_ReadReg(uiic0ps.Config.BaseAddress, XIICPS_IMR_OFFSET));
			DEBUG_POS();
		}
	}

}

/*****************************************************************************/
/**
*
* write some bytes to specified registers
*
*
* @param	reg_addr_offset is the offset address of the starting register.
* @param	value is the point of the value that will be written to chip
* @param	bytecnt is the byte number of value
*
* @return	XST_SUCCESS.
*
* @note		It's the user's responsibility to ensure bytecnt doesnot cross 
			recv_buffer's boarder and recv_buffer is ready to use before being used.
*
******************************************************************************/
void Adau1761_ReadBytes(u8 reg_addr_offset, u8 *recv_buffer, s32 bytecnt)
{
	u8 reg_addr[ADAU1761_SUBADDR_BYTENUM];

	/*
	 * Check the parameters
	 */
	DEBUG_CHECK(reg_addr_offset<(s32)ADAU1761_REG_NUM);	
	DEBUG_CHECK(bytecnt <= (s32)ADAU1761_REG_NUM - reg_addr_offset);

	//copy subaddress to a new array
	reg_addr[0U] = ADAU1761_SUBADDR_HIGH;
	reg_addr[1U] = reg_addr_offset;

	while(XIicPs_BusIsBusy(ADAU1761_CPU_IIC_PTR) == TRUE)
	{
		//NOP
	}

	uIic0TransStatus.SendComplete = FALSE;
	XIicPs_MasterSend(ADAU1761_CPU_IIC_PTR, reg_addr, ADAU1761_SUBADDR_BYTENUM,
		(u16)ADAU1761_CHIP_ADDR);
	while (!uIic0TransStatus.SendComplete) {
		if (0 != uIic0TransStatus.TotalErrorCount)
		{
			DEBUG("Event is 0x%x\n\r", XIicPs_ReadReg(uiic0ps.Config.BaseAddress,
				XIICPS_IMR_OFFSET));
			DEBUG_POS();
		}
	}
	
	while(XIicPs_BusIsBusy(ADAU1761_CPU_IIC_PTR) == TRUE)
	{
		//NOP
	}

	uIic0TransStatus.RecvComplete = FALSE;
	XIicPs_MasterRecv(ADAU1761_CPU_IIC_PTR, recv_buffer, bytecnt,
				ADAU1761_CHIP_ADDR);
	while (!uIic0TransStatus.RecvComplete) {
		if (0 != uIic0TransStatus.TotalErrorCount)
		{
			DEBUG("Event is 0x%x\n\r", XIicPs_ReadReg(uiic0ps.Config.BaseAddress, XIICPS_IMR_OFFSET));
			DEBUG_POS();
		}
	}

}


/*****************************************************************************/
/**
*
* initialize adau1761
* procedure:
* 			1. set pll control registers.
* 			2. wait for pll to be locked.
* 			3. enable core clock.
* 			4. set the rest registers.
*
* @param	None
*
* @return	None.
*
* @note		None.
*
******************************************************************************/

void Adau1761_Init()
{
	s32 i;
	u8 reg_data[ADAU1761_REG_NUM];
	u8 reg_reset_val[ADAU1761_REG_NUM];	//read out register values before any operation
	
	u8 pll_status_reg = 0x00;
	u8 pll_reg_pre_sets[6] = {
			0X02,	//02	//pll control setting, 0x0271 01dd 1b01.
			0X71,	//03	//Condition: MCLK = 24Mhz, fs = 44.1khz.
			0X01,	//04	//pll output = 45.1584Mhz = 1024 x fs.
			0XDD,	//05	//X = 2, R = 3, M = 625, N = 477
			0X1B,	//06	//refer to table 16 & 17, on page 28 of 93
			0X01	//07
		};

	u8 reg_sets[ADAU1761_REG_NUM];
	for(i=0;i<ADAU1761_REG_NUM;i++)
		reg_sets[i] = 0x00;

	//clear buffers
	for (i = 0; i < ADAU1761_REG_NUM; i++)
	{
		reg_data[i] = 0U;
		reg_reset_val[i] = 0U;
	}	
	Adau1761_ReadBytes(0x00, reg_reset_val, 7U);
	Adau1761_ReadBytes(0x08, &reg_reset_val[8], (s32)(ADAU1761_REG_NUM-8));

	//---------------------------------------------------------------------------
	//set core clk and pll
	//disable core clock
	Adau1761_ClkCtl_Set(ADAU1761_CLKSRC_PLL_MASK | ADAU1761_CLKFRE_1024FS_MASK |
						ADAU1761_CORECLK_DIS_MASK);

	//set pll control registers
	Adau1761_WriteBytes(ADAU1761_PLL_REGADDR, pll_reg_pre_sets, 6U);
	
	//wait for pll to be locked
	while((pll_status_reg & 0x02) == 0x00)
	{
		pll_status_reg = Adau1761_ReadReg(0x07);
	}

	//enable core clock
	Adau1761_ClkCtl_Set(ADAU1761_CLKSRC_PLL_MASK | ADAU1761_CLKFRE_1024FS_MASK |
						ADAU1761_CORECLK_EN_MASK);
	//---------------------------------------------------------------------------

	//---------------------------------------------------------------------------
	//code segment below are copied from https://blog.csdn.net/shichaog/article/details/51043268
	//and reedited, Aug 22,2019.
	Adau1761_WriteReg(ADAU1761_REC_MIXER_LEFT_CTL_0_ADDR, 0x01); //enable mixer 1, and mute left LINP/LINN
	Adau1761_WriteReg(ADAU1761_REC_MIXER_LEFT_CTL_1_ADDR, 0x07); //mute Left diff PGA input,connect left aux input to mxr 1 and set gain to 6 db
	Adau1761_WriteReg(ADAU1761_REC_MIXER_RIGHT_CTL_0_ADDR, 0x01); //enable mixer 2, and mute right RINP/RINN 
	Adau1761_WriteReg(ADAU1761_REC_MIXER_RIGHT_CTL_1_ADDR, 0x07); //unmute Right channel of line in into mxr 2 and set gain to 6 db

	Adau1761_WriteReg(ADAU1761_SERPORT_CTL_0_ADDR, 0x01);	//set iis port to master mode

	Adau1761_WriteReg(ADAU1761_ADC_CTL_ADDR, 0x13); //enable ADCs
 
	Adau1761_WriteReg(ADAU1761_PLAY_MIXER_LEFT_CTL_0_ADDR, 0x21); //unmute Left DAC into Mxr 3; enable mxr 3
	Adau1761_WriteReg(ADAU1761_PLAY_MIXER_RIGHT_CTL_0_ADDR, 0x41); //unmute Right DAC into Mxr4; enable mxr 4
	Adau1761_WriteReg(ADAU1761_PLAY_MIXER_LEFT_ADDR, 0x05); //unmute Mxr3 into Mxr5 and set gain to 6db; enable mxr 5
	Adau1761_WriteReg(ADAU1761_PLAY_MIXER_RIGHT_ADDR, 0x11); //unmute Mxr4 into Mxr6 and set gain to 6db; enable mxr 6
	Adau1761_WriteReg(ADAU1761_PLAY_LEFT_VOL_CTL_ADDR, 0xFF);//Mute Left channel of HP port (LHP)
	Adau1761_WriteReg(ADAU1761_PLAY_RIGHT_VOL_CTL_ADDR, 0xFF); //Mute Right channel of HP port (LHP)
	//AudioWriteToReg(ADAU1761_LINEOUT_LEFT_VOL_CTL_ADDR, 0xE6); //set LOUT volume (0db); unmute left channel of Line out port; set Line out port to line out mode
	//AudioWriteToReg(ADAU1761_LINEOUT_RIGHT_VOL_CTL_ADDR, 0xE6); // set ROUT volume (0db); unmute right channel of Line out port; set Line out port to line out mode
	Adau1761_WriteReg(ADAU1761_LINEOUT_LEFT_VOL_CTL_ADDR, 0xFE); //set LOUT volume (0db); unmute left channel of Line out port; set Line out port to line out mode
	Adau1761_WriteReg(ADAU1761_LINEOUT_RIGHT_VOL_CTL_ADDR, 0xFE); // set ROUT volume (0db); unmute right channel of Line out port; set Line out port to line out mode
	Adau1761_WriteReg(ADAU1761_PLAY_PWR_MGMT_ADDR, 0x03); //enable left and right channel playback (not sure exactly what this does...)
	Adau1761_WriteReg(ADAU1761_DAC_CTL_0_ADDR, 0x03); //enable both DACs
 
	Adau1761_WriteReg(ADAU1761_SER_IN_ROUTE_CTL_ADDR, 0x01); //Connect I2S serial port output (SDATA_O) to DACs
	Adau1761_WriteReg(ADAU1761_SER_OUT_ROUTE_CTL_ADDR, 0x01); //connect I2S serial port input (SDATA_I) to ADCs
 
	Adau1761_WriteReg(ADAU1761_CLKENABLE_0_ADDR, 0x7F); //Enable clocks
	Adau1761_WriteReg(ADAU1761_CLKENABLE_1_ADDR, 0x03); //Enable rest of clocks
	//---------------------------------------------------------------------------
	
	//read back the registers and print them on debug com port.
	//The registers are read back uncontinuously, as continuous reading will cause 
	//address offset, refer to the below blog for detailed info.
	//blog address: https://blog.csdn.net/ClamerKorallen/article/details/97776547
	
	/*************************************
	Adau1761_ReadBytes(0x00, reg_data, 7U);	//pause here for one moment, and read again.
	Adau1761_ReadBytes(0x08, &reg_data[8], (s32)(ADAU1761_REG_NUM-8));
	for(i=0;i<(s32)ADAU1761_REG_NUM;i++)
	{
		DEBUG("Register 0x%x	reset val: 0x%x	", i, reg_reset_val[i]);
		DEBUG("actual val: 0x%x\n\r", reg_data[i]);
	}
	************************************/

}


/*****************************************************************************/
/**
*
* Default adau1761 interrupt handler. May not be used.
*
*
* @param	None
*
* @return	None.
*
* @note		None.
*
******************************************************************************/

XIicPs_IntrHandler Adau1761_IntrHandler()
{
	DEBUG_POS();
}


/*****************************************************************************/
/**
*
* set volume
*
* @param	volume_dB
*			valstyle: ADAU1761_VOLUP		volume dB up, volume_dB is relative value
*					  ADAU1761_VOLDOWN		volume dB down, volume_dB is relative value
*					  ADAU1761_VOLABSOLUTE	volume dB set, volume_dB is absolute value
*
* @return	None.
*
* @note		None.
*
******************************************************************************/
void Adau1761_VolSet(u8 volume_dB, u8 valstyle)
{
	DEBUG_CHECK((valstyle == (u8)ADAU1761_VOLUP) ||
		(valstyle == (u8)ADAU1761_VOLDOWN) || (valstyle == (u8)ADAU1761_VOLABSOLUTE));
}

